---
title: 'Some gotchas'
---

# Some gotchas

## `useSnapshot(state)` without property access will always trigger re-render

Ref: https://github.com/pmndrs/valtio/issues/209#issuecomment-896859395

Suppose we have this state (or store).

```js
const state = proxy({
  obj: {
    count: 0,
    text: 'hello',
  },
})
```

If using the snapshot with accessing count,

```js
const snap = useSnapshot(state)
snap.obj.count
```

it will re-render only if `count` changes.

If the property access is obj,

```js
const snap = useSnapshot(state)
snap.obj
```

then, it will re-render if `obj` changes. This includes `count` changes and `text` changes.

Now, we can subscribe to the portion of the state.

```js
const snapObj = useSnapshot(state.obj)
snapObj
```

This is technically same as the previous one. It doesn't touch the property of `snapObj`, so it will re-render if `obj` changes.

In summary, if a snapshot object (nested or not) is not accessed with any properties, it assumes the entire object is accessed, so any change inside the object will trigger re-render.

## Using `React.memo` with object props may result in unexpected behavior (v1 only)

⚠️ This behavior is fixed in v2.

The `snap` variable returned by `useSnapshot(state)` is tracked for render optimization.
If you pass the `snap` or some objects in `snap` to a component with `React.memo`,
it may not work as expected because `React.memo` can skip touching object properties.

Side note: [react-tracked](https://react-tracked.js.org) has a special `memo` exported as a workaround.

We have some options:

<ol type="a">
  <li>Do not use `React.memo`.</li>
  <li>
    Do not pass objects to components with `React.memo` (pass primitive values
    instead).
  </li>
  <li>
    Pass in the proxy of that element, and then `useSnapshot` on that proxy.
  </li>
</ol>

### Example of (b)

```jsx
const ChildComponent = React.memo(
  ({
    title, // string or any primitive values are fine.
    description, // string or any primitive values are fine.
    // obj, // objects should be avoided.
  }) => (
    <div>
      {title} - {description}
    </div>
  ),
)

const ParentComponent = () => {
  const snap = useSnapshot(state)
  return (
    <div>
      <ChildComponent
        title={snap.obj.title}
        description={snap.obj.description}
      />
    </div>
  )
}
```

### Example of (c)

```jsx
const state = proxy({
  objects: [
    { id: 1, label: 'foo' },
    { id: 2, label: 'bar' },
  ],
})

const ObjectList = React.memo(() => {
  const stateSnap = useSnapshot(state)

  return stateSnap.objects.map((object, index) => (
    <Object key={object.id} objectProxy={state.objects[index]} />
  ))
})

const Object = React.memo(({ objectProxy }) => {
  const objectSnap = useSnapshot(objectProxy)

  return objectSnap.bar
})
```

## When to use `state` and when to use `snap` in functional components

- snap should be used in render function, every other cases state.
- callback functions are not in the render body and therefore state must be used.

```javascript
const Component = () => {
  // this is in render body
  const handleClick = () => {
    // this is NOT in render body
  }
  return <button onClick={handleClick}>button</button>
}
```

- deps in useEffect should be used extracting primitive values from snap. For example: `const { num, string, bool } = snap.watchObj`.
- changing a state value based on other state values (without involving values like props in a component), should preferably done outside react.

```javascript
subscribe(state.subscribeData, async () => {
  state.results = await load(state.someData)
})
```

## Controlled inputs may lose caret position without `sync: true`

Ref: https://github.com/pmndrs/valtio/issues/270

When using Valtio state with controlled `<input>` elements, you might notice the text caret jumping to the end while typing in the middle of the existing text.

This happens because Valtio batches state updates causing React to re-render after the input event. React resets the DOM value and loses the caret position.

**Use `{ sync: true }` to update synchronously and preserve the caret:**

```jsx
function Input() {
  const snap = useSnapshot(state, { sync: true })

  return (
    <input
      value={snap.text}
      onChange={(e) => {
        state.text = e.target.value
      }}
    />
  )
}
```

`sync: true` disables batching so React re-renders within the same event loop tick, skipping the DOM update and preserving the caret position.

## Issue with `array` `proxy`

The following use case can occur unexpected results on `arr` subscription:

```javascript
const byId = {}
arr.forEach((item) => {
  byId[item.id] = item
})
arr.splice(0, arr.length)
arr.push(newValue())
someUpdateFunc(byId)
Object.keys(byId).forEach((key) => arr.push(byId[key]))
```

[Issues](https://github.com/pmndrs/valtio/issues/712) may arise when handling the array proxy reference in the subsequent steps:

<ol type="a">
  <li>Subscribe array proxy</li>
  <li>Use the proxy as snapshot</li>
  <li>Assign temp variable for updating</li>
  <li>Remove proxy from the array</li>
  <li>Update temp</li>
  <li>Push temp in the original array</li>
</ol>

**Example issue case:**

```javascript
const a = proxy([
  {
    nested: {
      nested: {
        test: 'apple',
      },
    },
  },
])

const sa = snapshot(a) // b.

// a.
subscribe(a, () => {
  const updated = snapshot(a)
  console.log('this is updated proxy. test is Banana', a)
  console.log('however, for the snapshot of a, test is still apple', updated)
})

function handle() {
  const temp = a[0] // c.
  a.splice(0, 1) // d.
  temp.nested.nested.test = 'Banana' // e.
  a.push(temp) // f.
  console.log(Object.is(temp, a[0])) // this will be true
}
```

**To work around this, swap d and e:**

```javascript
// ...

function handle() {
  const temp = a[0]
  temp.nested.nested.test = 'Banana' // Update first remove from array
  a.splice(0, 1)
  a.push(temp)
}
// ...
```

If the workaround is not applied and you are using react with [devtools()](https://valtio.pmnd.rs/docs/api/utils/devtools), the redux devtools will notify a value update, but the snapshot will remain the same within the devtools' subscription.

As a result, the devtools will not display any state change.

Additionally, this issue involved not only updating devtools, but also triggering `re-render`.

## Issue with imports when using a library other than `react` (i.e. solidjs)

Valtio does not have to work within react, however it was built with react in mind. This being the case, the main `valtio` module exports the react modules alongside the vanilla modules for convenience. This means if you are attempting to import from the main `valtio` module or the `valtio/utils` submodule into a non-react project, you may end up with build errors like this:

```
 node_modules/.pnpm/valtio@2.1.4/node_modules/valtio/esm/react.mjs (2:18): "useRef" is not exported by "__vite-optional-peer-dep:react:valtio", imported by "node_modules/.pnpm/valtio@2.1.4/node_modules/valtio/esm/react.mjs".
```

This occurs because the main valtio module exports both framework-agnostic and React-specific functionality, causing build tools like Rollup to look for React dependencies even when they're not needed.

There is a simple fix for this, however. Instead of importing from the main `valtio` module like this:

```ts
import { proxy, snapshot, subscribe } from 'valtio'
```

you can import directly from the framework-agnostic `vanilla` submodule:

```ts
import { proxy, snapshot, subsribe } from 'valtio/vanilla'
// this also applies for the utils
import { proxyMap, deepClone } from 'valtio/vanilla/utils'
```
